package flexo.physic.entity;

import flexo.math.Vector;
import flexo.physic.PhysicSystem;
import flexo.physic.force.Force;
import flexo.scene.ExclusiveEntity;
import flexo.physic.collision.Shape;

public abstract class Entity extends ExclusiveEntity
{
	protected Vector position;
	protected Vector velocity;
	protected Vector acceleration;
	
	/**
	 * Inverse mass of the object. We store the inverse value for efficiency. 
	 */
	protected float inverseMass;
	
	/**
	 * We damp the movement to avoid numerical consistency
	 * and allow the object to release kinetic energy. 
	 */
	protected float damping;
	
	protected Vector localGravity;
	protected float size;
	
	protected java.util.List<Force> forceList;

	public Entity(float initialGravity, float mass)
	{
		super(Type.PHYSIC);
		
		velocity = new Vector();
		position = new Vector();
		acceleration = new Vector();
		localGravity = new Vector();
		
		if (mass <= 0)
		{
			inverseMass = 0;
		}
		else
		{
			inverseMass = 1 / mass;
		}
		
		setDamping(1);		
		setGravity(initialGravity);
		forceList = new java.util.LinkedList<Force>();
	}
	
	public abstract Shape getCollisionShape();
	
	/**
	 * Turns a position specified in worldSpace (world-space coordinates)
	 * into object-space coordinates. It overwrites the vector with the new value.
	 * @param worldSpace
	 */
	protected void calculateObjSpace(Vector worldSpace)
	{
		this.exclusiveNode.getAbsoluteTransformation().inverseTransform(worldSpace);
	}
	
	public float getSize()
	{
		return size;
	}
	
	public Vector getAcceleration()
	{
		return acceleration;
	}

	public float getInverseMass()
	{
		return this.inverseMass;
	}
	
	public void setGravity(float value)
	{
		localGravity.y = -value;
	}
	
	public void setDamping(float value)
	{
		if (value > 0 && value <= 1)
		{
			damping = value;
		}
	}
	
	public Vector getPosition()
	{
		return position;
	}
	
	public void setPosition(Vector value)
	{
		this.position.set(value);
		this.velocity.reset();
	}
	
	public void setVelocity(Vector value)
	{
		this.velocity.set(value);
	}
	
	public Vector getVelocity()
	{
		return velocity;
	}
	
	public void applyForce(String forceName)
	{
		PhysicSystem ps = (PhysicSystem)flexo.Kernel.getSystem(flexo.physic.PhysicSystem.ID);
		forceList.add(ps.getForce(forceName));
	}
	
	public void remove()
	{
		PhysicSystem ps = (PhysicSystem)flexo.Kernel.getSystem(flexo.physic.PhysicSystem.ID);
		ps.removeEntity(this);
	}

	public void integrate(float deltaTime)
	{
		if (isAttached() && inverseMass > 0 && deltaTime > 0)
		{
			// Update linear position
			position.addScaledVector(velocity, deltaTime);
			this.exclusiveNode.setPosition(position);
			
			// Calculate all forces
			Vector forceAccum = Vector.getTempVector();
			
			for (Force f : forceList)
			{
				forceAccum.move(f.calculateForce(this, deltaTime));
			}
			
			// Add local gravity
			forceAccum.move(this.localGravity);
			
			// Calculate acceleration
			acceleration.reset();
			acceleration.addScaledVector(forceAccum, this.inverseMass);
			
			// Update velocity based on acceleration
			velocity.addScaledVector(acceleration, deltaTime);
			
			// Damp the velocity if needed
			if (damping < 1)
			{
				velocity.scale((float)Math.pow(damping, deltaTime));
			}
		}
	}
}
